package telemetry

import (
	"context"
	"errors"
	"fmt"
	"time"

	"iot-cloud-platform/internal/database"
	"iot-cloud-platform/internal/logger"
	"iot-cloud-platform/internal/models"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

var (
	ErrInvalidTimeRange   = errors.New("invalid time range")
	ErrInvalidDeviceID    = errors.New("invalid device ID")
	ErrNoDataFound        = errors.New("no telemetry data found")
	ErrInvalidAggregation = errors.New("invalid aggregation type")
)

// TelemetryService handles telemetry data operations
type TelemetryService struct {
	db     *gorm.DB
	influx *database.InfluxManager
	redis  *database.RedisManager
}

// NewTelemetryService creates a new telemetry service
func NewTelemetryService(db *gorm.DB, influx *database.InfluxManager, redis *database.RedisManager) *TelemetryService {
	return &TelemetryService{
		db:     db,
		influx: influx,
		redis:  redis,
	}
}

// SubmitTelemetryRequest represents telemetry submission request
type SubmitTelemetryRequest struct {
	DeviceID  uuid.UUID              `json:"device_id" binding:"required"`
	Data      map[string]interface{} `json:"data" binding:"required"`
	Timestamp *time.Time             `json:"timestamp"`
	Location  *models.Location       `json:"location"`
	Quality   models.DataQuality     `json:"quality"`
}

// TelemetryQueryRequest represents telemetry query parameters
type TelemetryQueryRequest struct {
	DeviceID  uuid.UUID `form:"device_id" binding:"required"`
	StartTime time.Time `form:"start_time" binding:"required"`
	EndTime   time.Time `form:"end_time" binding:"required"`
	Fields    []string  `form:"fields"`
	Limit     int       `form:"limit,default=1000"`
	OrderBy   string    `form:"order_by,default=desc"`
}

// AggregatedQueryRequest represents aggregated data query parameters
type AggregatedQueryRequest struct {
	DeviceID    uuid.UUID `form:"device_id" binding:"required"`
	StartTime   time.Time `form:"start_time" binding:"required"`
	EndTime     time.Time `form:"end_time" binding:"required"`
	Field       string    `form:"field" binding:"required"`
	Aggregation string    `form:"aggregation,default=mean"`
	Window      string    `form:"window,default=1h"`
	GroupBy     []string  `form:"group_by"`
}

// TelemetryResponse represents telemetry query response
type TelemetryResponse struct {
	Data      []models.TelemetryData `json:"data"`
	Total     int                    `json:"total"`
	StartTime time.Time              `json:"start_time"`
	EndTime   time.Time              `json:"end_time"`
	DeviceID  uuid.UUID              `json:"device_id"`
}

// AggregatedResponse represents aggregated data response
type AggregatedResponse struct {
	Metrics   []models.TelemetryMetrics `json:"metrics"`
	DeviceID  uuid.UUID                 `json:"device_id"`
	Field     string                    `json:"field"`
	Window    string                    `json:"window"`
	StartTime time.Time                 `json:"start_time"`
	EndTime   time.Time                 `json:"end_time"`
}

// SubmitTelemetryData stores telemetry data from devices
func (ts *TelemetryService) SubmitTelemetryData(ctx context.Context, req SubmitTelemetryRequest) error {
	// Validate device exists
	var device models.Device
	if err := ts.db.First(&device, "id = ?", req.DeviceID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return ErrInvalidDeviceID
		}
		return fmt.Errorf("failed to validate device: %w", err)
	}

	// Set default timestamp if not provided
	timestamp := time.Now()
	if req.Timestamp != nil {
		timestamp = *req.Timestamp
	}

	// Set default quality if not provided
	quality := models.DataQualityGood
	if req.Quality != "" {
		quality = req.Quality
	}

	// Create telemetry data
	telemetryData := models.TelemetryData{
		DeviceID:  req.DeviceID,
		Timestamp: timestamp,
		Data:      req.Data,
		Location:  req.Location,
		Quality:   quality,
	}

	// Store in InfluxDB
	if err := ts.influx.WriteTelemetryData(ctx, telemetryData); err != nil {
		logger.GetLogger().WithError(err).Error("Failed to write telemetry data to InfluxDB")
		return fmt.Errorf("failed to store telemetry data: %w", err)
	}

	// Update device last seen in cache
	ts.redis.SetDeviceLastSeen(ctx, req.DeviceID.String(), timestamp.Unix())

	// Update device status to active if it was offline
	if device.Status == models.DeviceStatusOffline {
		ts.db.Model(&device).Update("status", models.DeviceStatusActive)
		ts.redis.SetDeviceStatus(ctx, req.DeviceID.String(), string(models.DeviceStatusActive), 3600)
	}

	logger.GetLogger().Debugf("Stored telemetry data for device %s", req.DeviceID)
	return nil
}

// SubmitBatchTelemetryData stores multiple telemetry data points efficiently
func (ts *TelemetryService) SubmitBatchTelemetryData(ctx context.Context, dataPoints []SubmitTelemetryRequest) error {
	if len(dataPoints) == 0 {
		return nil
	}

	// Convert to telemetry data format
	telemetryPoints := make([]models.TelemetryData, len(dataPoints))
	deviceIDs := make(map[uuid.UUID]bool)

	for i, req := range dataPoints {
		timestamp := time.Now()
		if req.Timestamp != nil {
			timestamp = *req.Timestamp
		}

		quality := models.DataQualityGood
		if req.Quality != "" {
			quality = req.Quality
		}

		telemetryPoints[i] = models.TelemetryData{
			DeviceID:  req.DeviceID,
			Timestamp: timestamp,
			Data:      req.Data,
			Location:  req.Location,
			Quality:   quality,
		}

		deviceIDs[req.DeviceID] = true
	}

	// Store in InfluxDB
	if err := ts.influx.WriteBatchTelemetryData(ctx, telemetryPoints); err != nil {
		logger.GetLogger().WithError(err).Error("Failed to write batch telemetry data to InfluxDB")
		return fmt.Errorf("failed to store batch telemetry data: %w", err)
	}

	// Update device statuses
	for deviceID := range deviceIDs {
		ts.redis.SetDeviceLastSeen(ctx, deviceID.String(), time.Now().Unix())
		ts.redis.SetDeviceStatus(ctx, deviceID.String(), string(models.DeviceStatusActive), 3600)
	}

	logger.GetLogger().Debugf("Stored batch telemetry data for %d points across %d devices", len(dataPoints), len(deviceIDs))
	return nil
}

// GetTelemetryData retrieves telemetry data for a device within a time range
func (ts *TelemetryService) GetTelemetryData(ctx context.Context, req TelemetryQueryRequest) (*TelemetryResponse, error) {
	// Validate time range
	if req.EndTime.Before(req.StartTime) {
		return nil, ErrInvalidTimeRange
	}

	// Validate device exists
	var device models.Device
	if err := ts.db.First(&device, "id = ?", req.DeviceID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrInvalidDeviceID
		}
		return nil, fmt.Errorf("failed to validate device: %w", err)
	}

	// Set default limit
	if req.Limit <= 0 || req.Limit > 10000 {
		req.Limit = 1000
	}

	// Query telemetry data from InfluxDB
	telemetryData, err := ts.influx.QueryTelemetryData(ctx, req.DeviceID, req.StartTime, req.EndTime, req.Limit)
	if err != nil {
		return nil, fmt.Errorf("failed to query telemetry data: %w", err)
	}

	// Filter fields if specified
	if len(req.Fields) > 0 {
		telemetryData = ts.filterTelemetryFields(telemetryData, req.Fields)
	}

	response := &TelemetryResponse{
		Data:      telemetryData,
		Total:     len(telemetryData),
		StartTime: req.StartTime,
		EndTime:   req.EndTime,
		DeviceID:  req.DeviceID,
	}

	return response, nil
}

// GetAggregatedData retrieves aggregated telemetry data
func (ts *TelemetryService) GetAggregatedData(ctx context.Context, req AggregatedQueryRequest) (*AggregatedResponse, error) {
	// Validate time range
	if req.EndTime.Before(req.StartTime) {
		return nil, ErrInvalidTimeRange
	}

	// Validate aggregation type
	validAggregations := map[string]bool{
		"mean": true, "avg": true, "sum": true, "count": true,
		"min": true, "max": true, "first": true, "last": true,
	}
	if !validAggregations[req.Aggregation] {
		return nil, ErrInvalidAggregation
	}

	// Validate device exists
	var device models.Device
	if err := ts.db.First(&device, "id = ?", req.DeviceID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrInvalidDeviceID
		}
		return nil, fmt.Errorf("failed to validate device: %w", err)
	}

	// Query aggregated data from InfluxDB
	metrics, err := ts.influx.QueryAggregatedData(ctx, req.DeviceID, req.Field, req.Aggregation, req.StartTime, req.EndTime, req.Window)
	if err != nil {
		return nil, fmt.Errorf("failed to query aggregated data: %w", err)
	}

	response := &AggregatedResponse{
		Metrics:   metrics,
		DeviceID:  req.DeviceID,
		Field:     req.Field,
		Window:    req.Window,
		StartTime: req.StartTime,
		EndTime:   req.EndTime,
	}

	return response, nil
}

// GetLatestTelemetryData retrieves the latest telemetry data for a device
func (ts *TelemetryService) GetLatestTelemetryData(ctx context.Context, deviceID uuid.UUID, fields []string) (*models.TelemetryData, error) {
	// Validate device exists
	var device models.Device
	if err := ts.db.First(&device, "id = ?", deviceID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrInvalidDeviceID
		}
		return nil, fmt.Errorf("failed to validate device: %w", err)
	}

	// Query latest data (last 24 hours)
	endTime := time.Now()
	startTime := endTime.Add(-24 * time.Hour)

	telemetryData, err := ts.influx.QueryTelemetryData(ctx, deviceID, startTime, endTime, 1)
	if err != nil {
		return nil, fmt.Errorf("failed to query latest telemetry data: %w", err)
	}

	if len(telemetryData) == 0 {
		return nil, ErrNoDataFound
	}

	latest := telemetryData[0]

	// Filter fields if specified
	if len(fields) > 0 {
		filteredData := make(map[string]interface{})
		for _, field := range fields {
			if value, exists := latest.Data[field]; exists {
				filteredData[field] = value
			}
		}
		latest.Data = filteredData
	}

	return &latest, nil
}

// GetDeviceDataSummary retrieves a summary of device data
func (ts *TelemetryService) GetDeviceDataSummary(ctx context.Context, deviceID uuid.UUID, days int) (*DeviceDataSummary, error) {
	// Validate device exists
	var device models.Device
	if err := ts.db.First(&device, "id = ?", deviceID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrInvalidDeviceID
		}
		return nil, fmt.Errorf("failed to validate device: %w", err)
	}

	if days <= 0 {
		days = 7 // Default to 7 days
	}

	endTime := time.Now()
	startTime := endTime.Add(-time.Duration(days) * 24 * time.Hour)

	// Get raw data count
	telemetryData, err := ts.influx.QueryTelemetryData(ctx, deviceID, startTime, endTime, 100000)
	if err != nil {
		return nil, fmt.Errorf("failed to query telemetry data for summary: %w", err)
	}

	// Calculate summary
	summary := &DeviceDataSummary{
		DeviceID:    deviceID,
		StartTime:   startTime,
		EndTime:     endTime,
		TotalPoints: len(telemetryData),
		Fields:      make(map[string]FieldSummary),
	}

	if len(telemetryData) > 0 {
		summary.FirstTimestamp = telemetryData[len(telemetryData)-1].Timestamp
		summary.LastTimestamp = telemetryData[0].Timestamp

		// Analyze fields
		fieldStats := make(map[string]*FieldStats)

		for _, data := range telemetryData {
			for field, value := range data.Data {
				if stats, exists := fieldStats[field]; !exists {
					fieldStats[field] = &FieldStats{
						Count:      1,
						FirstValue: value,
						LastValue:  value,
					}

					if numValue, ok := value.(float64); ok {
						stats = fieldStats[field]
						stats.NumericValues = []float64{numValue}
						stats.Sum = numValue
						stats.Min = numValue
						stats.Max = numValue
					}
				} else {
					stats.Count++
					stats.LastValue = value

					if numValue, ok := value.(float64); ok {
						stats.NumericValues = append(stats.NumericValues, numValue)
						stats.Sum += numValue
						if numValue < stats.Min {
							stats.Min = numValue
						}
						if numValue > stats.Max {
							stats.Max = numValue
						}
					}
				}
			}
		}

		// Convert to field summaries
		for field, stats := range fieldStats {
			fieldSummary := FieldSummary{
				Count:      stats.Count,
				FirstValue: stats.FirstValue,
				LastValue:  stats.LastValue,
			}

			if len(stats.NumericValues) > 0 {
				fieldSummary.IsNumeric = true
				fieldSummary.Sum = &stats.Sum
				fieldSummary.Min = &stats.Min
				fieldSummary.Max = &stats.Max
				avg := stats.Sum / float64(len(stats.NumericValues))
				fieldSummary.Average = &avg
			}

			summary.Fields[field] = fieldSummary
		}
	}

	return summary, nil
}

// Helper structures
type DeviceDataSummary struct {
	DeviceID       uuid.UUID               `json:"device_id"`
	StartTime      time.Time               `json:"start_time"`
	EndTime        time.Time               `json:"end_time"`
	TotalPoints    int                     `json:"total_points"`
	FirstTimestamp time.Time               `json:"first_timestamp"`
	LastTimestamp  time.Time               `json:"last_timestamp"`
	Fields         map[string]FieldSummary `json:"fields"`
}

type FieldSummary struct {
	Count      int         `json:"count"`
	FirstValue interface{} `json:"first_value"`
	LastValue  interface{} `json:"last_value"`
	IsNumeric  bool        `json:"is_numeric"`
	Sum        *float64    `json:"sum,omitempty"`
	Min        *float64    `json:"min,omitempty"`
	Max        *float64    `json:"max,omitempty"`
	Average    *float64    `json:"average,omitempty"`
}

type FieldStats struct {
	Count         int
	FirstValue    interface{}
	LastValue     interface{}
	NumericValues []float64
	Sum           float64
	Min           float64
	Max           float64
}

// filterTelemetryFields filters telemetry data to include only specified fields
func (ts *TelemetryService) filterTelemetryFields(data []models.TelemetryData, fields []string) []models.TelemetryData {
	fieldSet := make(map[string]bool)
	for _, field := range fields {
		fieldSet[field] = true
	}

	for i := range data {
		filteredData := make(map[string]interface{})
		for field, value := range data[i].Data {
			if fieldSet[field] {
				filteredData[field] = value
			}
		}
		data[i].Data = filteredData
	}

	return data
}

// DeleteTelemetryData deletes telemetry data for a device within a time range
func (ts *TelemetryService) DeleteTelemetryData(ctx context.Context, deviceID uuid.UUID, startTime, endTime time.Time) error {
	// Validate device exists
	var device models.Device
	if err := ts.db.First(&device, "id = ?", deviceID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return ErrInvalidDeviceID
		}
		return fmt.Errorf("failed to validate device: %w", err)
	}

	// Validate time range
	if endTime.Before(startTime) {
		return ErrInvalidTimeRange
	}

	// Delete from InfluxDB (implementation would depend on InfluxDB client)
	// For now, we'll log the operation
	logger.GetLogger().Infof("Deleting telemetry data for device %s from %s to %s", deviceID, startTime, endTime)

	return nil
}
